﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using Hopscotch.Domain.Common.StateTracking;
using Newtonsoft.Json;

namespace Hopscotch.Domain.Common
{
    [DataContract(IsReference = true)]
    public abstract class DomainBase<TKey> :
        IObjectWithChangeTracker,
        INotifyPropertyChanged,
        IDataErrorInfo,
        ICloneable
        where TKey : struct
    {
        [DataMember]
        public abstract TKey Id { get; set; }
        private byte[] _version;
        private Nullable<int> _modifiedBy;
        private Nullable<DateTime> _modifiedDt;

        protected bool IsDeserializing { get; private set; }
        private event PropertyChangedEventHandler propertyChanged;
        private ObjectChangeTracker changeTracker;

        public DomainBase()
        {
        }

        public DomainBase(DomainBase<TKey> copy)
        {
            Merge(copy);
        }

        public virtual void Merge(DomainBase<TKey> copy)
        {
            _version = copy.Version;
            _modifiedBy = copy.ModifiedBy;
            _modifiedDt = copy.ModifiedDt;
        }

        [DataMember]
        public byte[] Version
        {
            get { return _version; }
            set
            {
                if (_version != value)
                {
                    ChangeTracker.RecordOriginalValue("Version", _version);
                    _version = value;
                    OnPropertyChanged();
                }
            }
        }

        [DataMember]
        public Nullable<int> ModifiedBy
        {
            get { return _modifiedBy; }
            set
            {
                if (_modifiedBy != value)
                {
                    ChangeTracker.RecordOriginalValue("ModifiedBy", _modifiedBy);
                    _modifiedBy = value;
                    OnPropertyChanged();
                }
            }
        }

        [DataMember]
        public Nullable<System.DateTime> ModifiedDt
        {
            get { return _modifiedDt; }
            set
            {
                if (_modifiedDt != value)
                {
                    ChangeTracker.RecordOriginalValue("ModifiedDt", _modifiedDt);
                    _modifiedDt = value;
                    OnPropertyChanged();
                }
            }
        }
        
        #region IDataErrorInfo

        public bool IsValid
        {
            get
            {
                ValidationContext vc = new ValidationContext(this, null, null);
                List<ValidationResult> result = new List<ValidationResult>();

                bool valid = Validator.TryValidateObject(this, vc, result, true);
                return valid;
            }
        }

        public string Error
        {
            get
            {
                ValidationContext vc = new ValidationContext(this, null, null);
                List<ValidationResult> result = new List<ValidationResult>();
                var sb = new StringBuilder();

                if (!Validator.TryValidateObject(this, vc, result, true))
                {
                    int i = 0;
                    foreach (ValidationResult vr in result)
                    {
                        if (i > 0)
                            sb.AppendLine();
                        sb.Append(vr.ErrorMessage);

                        i++;
                    }
                }
                return sb.ToString();
            }
        }

        public string this[string columnName]
        {
            get
            {
                ValidationContext vc = new ValidationContext(this, null, null);
                List<ValidationResult> result = new List<ValidationResult>();

                vc.MemberName = columnName;
                if (!Validator.TryValidateProperty(GetType().GetProperty(columnName).GetValue(this, null), vc, result))
                {
                    return result.First().ErrorMessage;
                }
                return null;
            }
        }
        #endregion

        #region Equals

        /// <summary>
        /// Gets the hashcode for this object.
        /// </summary>
        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            // Check for reference equality.
            if (ReferenceEquals(this, obj))
                return true;

            // Check for exact type match (prefer symmetry)
            else if (GetType() != obj.GetType())
                return false;

            var entity = obj as DomainBase<TKey>;

            //Check each field
            return entity != null &&
                this.Id.Equals(entity.Id) &&
                this.Version == entity.Version &&
                this.ModifiedBy == entity.ModifiedBy &&
                this.ModifiedDt == entity.ModifiedDt;
        }

        #endregion

        #region Clone

        public abstract object Clone();
        #endregion

        #region ChangeTracking

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (ChangeTracker.State != ObjectState.Added && ChangeTracker.State != ObjectState.Deleted)
            {
                ChangeTracker.State = ObjectState.Modified;
            }
            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnNavigationPropertyChanged(String propertyName)
        {
            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged
        {
            add { propertyChanged += value; }
            remove { propertyChanged -= value; }
        }

        [DataMember]
        public ObjectChangeTracker ChangeTracker
        {
            get
            {
                if (changeTracker == null)
                {
                    changeTracker = new ObjectChangeTracker();
                    changeTracker.ObjectStateChanging += HandleObjectStateChanging;
                }
                return changeTracker;
            }
            protected set
            {
                if (changeTracker != null)
                {
                    changeTracker.ObjectStateChanging -= HandleObjectStateChanging;
                }
                changeTracker = value;
                if (changeTracker != null)
                {
                    changeTracker.ObjectStateChanging += HandleObjectStateChanging;
                }
            }
        }

        private void HandleObjectStateChanging(object sender, ObjectStateChangingEventArgs e)
        {
            if (e.NewState == ObjectState.Deleted)
            {
                ClearNavigationProperties();
            }
        }

        protected virtual void ClearNavigationProperties()
        {
        }

        [OnDeserializing]
        private void OnDeserializingMethod(StreamingContext context)
        {
            IsDeserializing = true;
        }

        [OnDeserialized]
        private void OnDeserializedMethod(StreamingContext context)
        {
            IsDeserializing = false;
            ChangeTracker.ChangeTrackingEnabled = true;
        }
        #endregion

    }
}
